"""
python p3_genetic_deap.py
"""

import json
import os
import numpy as np
import random
from deap import base, creator, tools, algorithms
from datetime import datetime
import read_4_json as r4j


# Load data from your provided module
data_warehouse =            r4j.load_warehouse_data()
data_category_inventory =   r4j.load_average_inventory_data()
data_category_sales =       r4j.load_average_sales_data()

split3_data_category_inventory = r4j.split3(data_category_inventory)
split3_data_category_sales =     r4j.split3(data_category_sales)

data_category_association = r4j.load_association_data()
mx_category_association = r4j.create_adjacency_matrix()

NUM_W = r4j.NUM_W
NUM_C = r4j.NUM_C


def utilization_score(x):
    if x < 0:
        raise ValueError("传入利用率分数函数里面的 x 必须是非负的")

    if 0 <= x <= 1:
        return x * 100.0
    if x > 1:
        return 100.0 / x


def cal_4f_fitness(coords):
    rounded_coords = np.round(coords).astype(int)

    f1 = 0
    f2 = 0
    f3 = 0
    f4 = 0

    # -----f1-----
    w_bucket = [0] * NUM_W
    for x in rounded_coords:
        wid = x
        w_bucket[wid] += 1

    for i in range(NUM_W):
        if w_bucket[i] > 0:
            f1 += data_warehouse[i]['daily_cost']
            
    ## 原来的f1的范围是[0, 1045253]
    # 所以做一个颠倒
    f1 = 1111111 - f1

    # -----f2 f3-----
    sum_inventory_all_w = [0] * NUM_W
    sum_sales_all_w = [0] * NUM_W

    for idx, val in enumerate(rounded_coords):
        cid = idx
        wid = val
        sum_inventory_all_w[wid] += data_category_inventory[cid]['average_inventory']
        sum_sales_all_w[wid] += data_category_sales[cid]['average_sales']

    for i in range(NUM_W):
        f2 += utilization_score(sum_inventory_all_w[i] / data_warehouse[i]['max_inventory'])
        f3 += utilization_score(sum_sales_all_w[i] / data_warehouse[i]['max_sales'])

    # -----f4-----
    cinw_mx = [[] for _ in range(NUM_W)]
    for idx, val in enumerate(rounded_coords):
        cid = idx
        wid = val
        cinw_mx[wid].append(cid)

    for row_index, row in enumerate(cinw_mx):
        len_thisw = len(row)
        for i in range(len_thisw):
            for j in range(len_thisw):
                x = row[i]
                y = row[j]
                
                f4 += mx_category_association[x][y]

    return f1, f2, f3, f4, f1 * f2 * f3 * f4


def obj_func(coords):
    _, _, _, _, res = cal_4f_fitness(coords=coords)
    return res


# Fitness evaluation function
def evaluate(individual):
    f1, f2, f3, f4, fitness_value = cal_4f_fitness(np.array(individual))
    return fitness_value


# Define individual and population
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)

toolbox = base.Toolbox()
toolbox.register("attr_warehouse", random.randint, 0, NUM_W - 1)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_warehouse, NUM_C)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

# Genetic algorithm functions
toolbox.register("evaluate", evaluate)
toolbox.register("mate", tools.cxUniform, indpb=0.05)
toolbox.register("mutate", tools.mutUniformInt, low=0, up=NUM_W - 1, indpb=0.1)
toolbox.register("select", tools.selTournament, tournsize=3)

# Global variable to track best solution
absolute_best_individual = None
absolute_best_fitness = float("-inf")


# Logging best solution
def log_best_solution(individual):
    global absolute_best_individual, absolute_best_fitness
    # 修改这里，直接获取适应度值
    fitness_value = toolbox.evaluate(individual)

    # 检查当前个体的适应度是否优于已记录的全局最佳适应度
    if fitness_value > absolute_best_fitness:
        absolute_best_individual = individual[:]
        absolute_best_fitness = fitness_value

        # 定义文件夹路径
        BEST_OUTPUT_DIR = "../fujian/p2/deap_genetic_4t/best_ever/"
        BEST_SOLUTION_FILE = "best_ever_solution.json"
        BEST_INDIVIDUAL_FILE = "best_ever_individual.log"

        # 确保输出目录存在
        os.makedirs(BEST_OUTPUT_DIR, exist_ok=True)

        # 构建完整的文件路径
        BEST_SOLUTION_PATH = os.path.join(BEST_OUTPUT_DIR, BEST_SOLUTION_FILE)
        BEST_INDIVIDUAL_PATH = os.path.join(BEST_OUTPUT_DIR, BEST_INDIVIDUAL_FILE)

        # 假设 'individual' 是你之前定义的个体
        solution = [{"category_id": i, "warehouse_id": wid} for i, wid in enumerate(individual)]

        # 将解写入 JSON 文件
        with open(BEST_SOLUTION_PATH, "w") as f:
            json.dump(solution, f, indent=4)

        # 计算适应度并记录最佳个体的适应度和目标值
        f1, f2, f3, f4, _ = cal_4f_fitness(np.array(individual))
        with open(BEST_INDIVIDUAL_PATH, "a") as log_file:
            log_file.write(f"{datetime.now()} - Best fitness: {fitness_value}, f1: {f1}, f2: {f2}, f3: {f3}, f4: {f4}\n")

# Main genetic algorithm process
def main():
    random.seed(64)
    pop = toolbox.population(n=300)

    NGEN = 5000
    CXPB, MUTPB = 0.5, 0.2

    for gen in range(NGEN):
        offspring = toolbox.select(pop, len(pop))
        offspring = list(map(toolbox.clone, offspring))

        for child1, child2 in zip(offspring[::2], offspring[1::2]):
            if random.random() < CXPB:
                toolbox.mate(child1, child2)
                del child1.fitness.values
                del child2.fitness.values

        for mutant in offspring:
            if random.random() < MUTPB:
                toolbox.mutate(mutant)
                del mutant.fitness.values

        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        fitnesses = map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = (fit,)

        pop[:] = offspring

        best_ind = tools.selBest(pop, 1)[0]
        log_best_solution(best_ind)

        if absolute_best_individual:
            pop[random.randint(0, len(pop) - 1)] = creator.Individual(absolute_best_individual)

if __name__ == "__main__":
    main()
